package org.slusk.thynwor.feedback;

import java.util.LinkedList;

import org.slusk.thynwor.corestructures.Drone;
import org.slusk.thynwor.corestructures.Drone.MoveOrder;
import org.slusk.thynwor.corestructures.Entity;
import org.slusk.thynwor.corestructures.Environment;
import org.slusk.thynwor.corestructures.Food;
import org.slusk.thynwor.corestructures.GridMap;
import org.slusk.thynwor.corestructures.Region.RegionType;
import org.slusk.thynwor.util.Coordinates;

import com.anji.hyperneat.nd.NDFloatArray;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback;
import com.anji.hyperneat.onlinereinforcement.trainingbank.Feedback.FeedbackType;

public class BackPropReinforcementProvider extends ReinforcementProvider {

	public BackPropReinforcementProvider(Environment environment) {
		super(environment);
	}
	
	@Override
	public Feedback getFeedback(Drone bot, MoveOrder mo, NDFloatArray inputs, Coordinates outputCoords, NDFloatArray outputs) {
		Feedback feedback = getReinforcement(bot, mo, outputCoords, outputs);
		return feedback;
	}

	protected Feedback getReinforcement(Drone bot, MoveOrder mo, Coordinates outputCoords, NDFloatArray outputs) {
		NDFloatArray feedback = null;
		boolean isTowingResource = bot.isTowingResource();	
		Coordinates coords = bot.getCoordinates();
		int rewardFactor = 0;
		LinkedList<Coordinates> signals = environment.buildSignalCoordinatesList();
		
		Entity entity = map().isInBounds(mo.proposedCoordinates) ? map().get(mo.proposedCoordinates).getContents() : null;

		// Did it pickup food?
		if (null != entity && entity instanceof Food && !isTowingResource) {
			rewardFactor = 1;
				
			// Did it assist with food?
//			Food f = (Food) entity;
//			if (!f.isBeingTowed() && f.getInteractingAgents().size() > 0) {
//				rewardFactor = environment.getRFAssistedWithFood();
//			}
		}
		
		// Did it bump something
		else if (null != entity || !map().isInBounds(mo.proposedCoordinates)) {
			rewardFactor = -1;
		}
		
		// Move toward signal
		else if (!isTowingResource && signals.size() > 0) {
			// If it moves toward any signal, that's good enough
			for (Coordinates signal : signals) {
				if (GridMap.calculateSpecialManhattanDistance(mo.proposedCoordinates, signal) < GridMap.calculateSpecialManhattanDistance(coords, signal)) {
					rewardFactor = 1;
					break;
				}
			}
		}

		// Did it drop food?
		else if (isTowingResource && GridMap.calculateSpecialManhattanDistance(mo.proposedCoordinates, bot.getInteractee().getCoordinates()) > 2) {
			rewardFactor = -1;
		}
		
		// Did it deliver food?
		else if (isTowingResource && map().isInRegion(mo.proposedCoordinates, RegionType.BASE)) {
			rewardFactor = 1;
		}
		
		// Did it move toward the goal while carrying food?
		else if (isTowingResource && 
				GridMap.calculateSpecialManhattanDistance(mo.proposedCoordinates, map().getClosestRegionOfType(mo.proposedCoordinates, RegionType.BASE).getCenter())
				< GridMap.calculateSpecialManhattanDistance(coords, map().getClosestRegionOfType(coords, RegionType.BASE).getCenter())) {
			rewardFactor = 1;
		}
		// Did it move away from the goal while carrying food?
//		else if (isTowingResource &&
//				GridMap.calculateSpecialManhattanDistance(mo.proposedCoordinates, map.getClosestRegionOfType(mo.proposedCoordinates, RegionType.BASE).getCenter())
//				>   GridMap.calculateSpecialManhattanDistance(coords, map.getClosestRegionOfType(coords, RegionType.BASE).getCenter())) {
//			rewardFactor = -1;
//		}
		
		feedback = processFeedbackForReinforcement(rewardFactor, outputCoords, outputs);
		
		return new Feedback(feedback, rewardFactor == 1 ? FeedbackType.REINFORCEMENT : rewardFactor == -1 ? FeedbackType.PUNISHMENT : FeedbackType.NEUTRAL);
	}

	
	protected NDFloatArray processFeedbackForReinforcement(double rewardFactor, Coordinates outputCoords, NDFloatArray outputs) {
		NDFloatArray feedback = outputs.clone();
		if (rewardFactor != 0) {
			// reward factor will be 1 or -1; this needs to translate to 1 or 0.
			feedback.set((float) (rewardFactor == 1 ? 1 : 0), outputCoords.x, outputCoords.y, 0);
		}		
		return feedback;
	}
}
